/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.google.code.twiddling.core.io;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import com.google.code.twiddling.core.bootstrap.TwiddlingShell;
import com.google.code.twiddling.core.context.Environment;
import com.google.code.twiddling.core.layout.LayoutManager;

/**
 * This class implements helper functions for the console to
 * 
 * <li> get a Command from the Terminal thru the input stream </li>
 * <li> get a Command String from the Console thru the input stream </li>
 * <li> get user input from the console </li>
 * <li> output text to the Terminal thru the Console output stream </li>
 * 
 * The input part of this class is not supposed to be accessed concurrently.
 * The output part however can. 
 * 
 * @author <a href="mailto:howard.gao@gmail.com">Howard Gao</a> 
 */
public class CopyOfBasicIOManager extends SystemIO implements IOManager{

	private UserInputListener listener;
	private CommandListener cmdListener;
	private InputBuffer buffer;
	private UserInputThread inputThread;
	private ProcessThread processThread;
	private boolean pageMode;
	private boolean started = false;
	private Environment environemnt;
	private Reader reader;
	
	public CopyOfBasicIOManager(Environment environment) {
		super();
		this.environemnt = environment;
		this.reader = new InputStreamReader(inputStream);
		buffer = new InputBuffer();
		pageMode = false;
	}
	
	
	/**
	 * start the IO
	 */
	public void start() {
		if (started) return;
		inputThread = new UserInputThread();
		inputThread.start();
		processThread = new ProcessThread();
		processThread.start();
		showBanner();
		showProfile();
		started = true;
	}
	
	private void showBanner() {
		println("");
		println("");
		println("");
			
		LayoutManager.getBannerLayout().setBanner(TwiddlingShell.BANNER)
									   .setVersion(TwiddlingShell.VERSION)
									   .setHomepage(TwiddlingShell.HOMEPAGE)
									   .print(this.out);
		println("");
		out.flush();
	}


	/*
	 * stop the IO
	 */
	public void stop() {
		if (!started) return;
		processThread.shutdown();
		try {
			processThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		inputThread.shutdown();
		try {
			inputThread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		inputThread = null;
		processThread = null;
		buffer.reset();
		started = false;
	}

	private void showProfile() {
		out.print(environemnt.getProfile().toString());
		out.flush();
	}

	private void newInputChar(Character c) {
		if (listener != null) {
			listener.onNewInput(c);
		} else {
			buffer.newInputChar(c);
		}
	}

	/**
	 * get the user's raw input string and returns.
	 * this helper reads the terminal input stream until a carriage return is read.
	 * @return the full string.
	 */
	public String getCommandString(long timeout) {
		return buffer.getCommandString(timeout);
	}

	/**
	 * read one char from the terminal input and returns immediately.
	 */
	public char getUserInput() {
		return ' ';
	}
	
	/**
	 * register an input listener to respond to the user input.
	 */
	public void registerUserInputListener(UserInputListener inputListener) {
		listener = inputListener;
	}
	
	/**
	 * TODO: Need to re-work, at least some consolidation with UserInputListener.
	 * @param listener
	 */
	public void registerCommandListener(CommandListener listener) {
		cmdListener = listener;
	}

	public void println(String string) {
		out.println(string);
		out.flush();
	}

	public void print(String string) {
		out.print(string);
		out.flush();
	}
	
	private class ProcessThread extends Thread {
		
		private CopyOfBasicIOManager owner = CopyOfBasicIOManager.this;
		private boolean shutdown;
		
		public ProcessThread() {
			super("ProcessThread");
			shutdown = false;
		}
		
		public void shutdown() {
			shutdown = true;
		}
		
		public void run() {
			while (!shutdown) {
				String cmd = owner.getCommandString(50);
				if (cmd != null) {
					cmd = cmd.trim();
					owner.cmdListener.process(cmd);
					if (!"exit".equals(cmd)) {
						owner.showProfile();
					}
				}
			}
		}
	}

	private class UserInputThread extends Thread {
		
		private CopyOfBasicIOManager owner = CopyOfBasicIOManager.this;
		private boolean shutdown;
		
		public UserInputThread() {
			super("UserInputThread");
			shutdown = false;
		}
		
		public void shutdown() {
			shutdown = true;
		}
		
		public void run() {
			while (!shutdown) {
				boolean rdy = false;
				try {
					rdy = owner.getReader().ready();
				} catch (IOException e1) {
				}
				if (!rdy) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
					}
				} else {
					try {
						int inChar = owner.getReader().read();
						owner.newInputChar(Character.valueOf((char)inChar));
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	public Reader getReader() {
		return this.reader;
	}
	
	public IO getIO() {
		return this;
	}
	
}
